package graphic;
import flash.display.DisplayObjectContainer;
import flash.display.Loader;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.Lib;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.net.URLLoaderDataFormat;
import format.zip.Data;
import haxe.io.Bytes;
import haxe.io.BytesData;
import haxe.io.BytesInput;
import haxe.io.Input;
import haxe.xml.Fast;
import format.zip.Reader;

class RessourceManagerEvent
{
	public static var INITIALISATION_COMPLETE 	= "Ressource_manager_initialisation_complete";
	public static var PREPARATION_COMPLETE		= "Ressource_manager_Preparation_complete";
}

/**
 * @author Damien Mabin
 */

class RessourceManager extends EventDispatcher
{
	private static var s_SingletonInstance	: RessourceManager = new RessourceManager();
	private var m_AvailableAssets			: Hash<{ file : String, xml : Fast}>;
	private var m_LoadedAssets				: Hash<{ ro:RenderObject, file : String, domain : ApplicationDomain }>;
	private var m_CurrentlyLoading			: Array<EventDispatcher>;
	
	private function new() 
	{
		super();
		
		m_CurrentlyLoading = new Array<EventDispatcher>();
		m_AvailableAssets = new Hash<{ file : String, xml : Fast}>();
		m_LoadedAssets = new Hash<{ ro:RenderObject, file : String, domain : ApplicationDomain }>();
		
		var ressourceDescriptor : URLLoader = new URLLoader();
		ressourceDescriptor.addEventListener(Event.COMPLETE, OnRessourceDescriptorLoadingComplete);
		ressourceDescriptor.load(new flash.net.URLRequest("RessourceManager.xml"));
	}
	
	public static inline function GetInstance():RessourceManager
	{
		if (s_SingletonInstance == null)
		{
			s_SingletonInstance = new RessourceManager();
		}
		return s_SingletonInstance;
	}
	
	public function InstanciateRenderObject(_ModelName:String):RenderObject
	{
		var info : { ro:RenderObject, file : String, domain : ApplicationDomain } = m_LoadedAssets.get(_ModelName);
		if (info != null)
		{
			return info.ro.Clone();
		}
		else
		{
			return null;
		}
	}
	
	public function PrepareRenderObjects(_RenderObjects:Array<{ modelName:String, file:String }>):Void
	{
		var renderObjectBySwf : Array<{ file:String, renderObjects:Array<String> }> = new Array<{ file:String, renderObjects:Array<String> }>();
		
		for (toPrepareRO in _RenderObjects)
		{
			var askedRenderObject : { file:String, renderObjects:Array<String> } = null;
			for (elt in renderObjectBySwf)
			{
				if (elt.file == toPrepareRO.file)
				{
					askedRenderObject = elt;
					break;
				}
			}
			
			if (askedRenderObject != null)
			{
				var renderObjectAlreadyAsked : Bool = false;
				for (ro in askedRenderObject.renderObjects)
				{
					if (ro == toPrepareRO.modelName)
					{
						renderObjectAlreadyAsked = true;
						break;
					}
				}
				if (!renderObjectAlreadyAsked)
				{
					askedRenderObject.renderObjects.push(toPrepareRO.modelName);
				}
			}
			else
			{
				var ros:Array<String> = new Array<String>();
				ros.push(toPrepareRO.modelName);
				renderObjectBySwf.push({file:toPrepareRO.file, renderObjects:ros});
			}
		}
		
		// Here renderObjectBySwf contain all the render object to load, sorted by swf file
		#if debug
		for(swfToLoad in renderObjectBySwf)
		{
			trace("File to load : " + swfToLoad.file + " for RO : ");
			for (ro in swfToLoad.renderObjects)
			{
				trace("     - " + ro);
			}
		}
		#end
		
		for (swfToLoad in renderObjectBySwf)
		{
			if (swfToLoad.file.substr(swfToLoad.file.length - 3) == "swf")
			{
				var swfLoader : Loader = new Loader();
				swfLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, callback(OnSwfLoaded, swfToLoad.file, swfToLoad.renderObjects));
				swfLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, OnError);
				swfLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, OnError);
				m_CurrentlyLoading.push(swfLoader.contentLoaderInfo);
				swfLoader.load(new URLRequest(swfToLoad.file), new flash.system.LoaderContext(false, new flash.system.ApplicationDomain()));
			}
			else if (swfToLoad.file.substr(swfToLoad.file.length - 3) == "swc")
			{
				// Qu'est ce qu'on doit faire alors?
				// 		- On doit choper le contenu du swf dans le swc.
				//		- Trouver un moyen de feeder un Loader avec le contenu
				var ressourceFile : URLLoader = new URLLoader();
				ressourceFile.dataFormat = URLLoaderDataFormat.BINARY;
				m_CurrentlyLoading.push(ressourceFile);
				ressourceFile.addEventListener(Event.COMPLETE, callback(OnSwcLoaded, swfToLoad.file, swfToLoad.renderObjects));
				ressourceFile.load(new URLRequest(swfToLoad.file));
			}
		}
	}
	
	public function GetAvailableRenderObjects():Array<{name : String, file : String}>
	{
		var res : Array<{name : String, file : String}> = new Array();
		for (elt in m_AvailableAssets.keys())
		{
			res.push({name:elt, file:m_AvailableAssets.get(elt).file});
		}
		return res;
	}
	
	private function OnSwcLoaded(_FilePath : String, _RenderObjectToLoad:Array<String>, _Evt : Event):Void
	{
		#if debug
			trace("Swc loaded : " + _FilePath);
		#end
		
		var haxeIOBytesData : BytesData = _Evt.target.data;
		var haxeIOBytes : Bytes = Bytes.ofData(haxeIOBytesData);
		var haxeIO : Input = new BytesInput(haxeIOBytes);
		
		var reader : Reader = new Reader(haxeIO);
		var zipData:Data = reader.read();
		
		for (elem in zipData.iterator())
		{
			trace("tada : " + elem.fileName);
			if (elem.fileName == "library.swf")
			{
				//var hxIO : Input = new BytesInput(elem.data);
				var data = elem.data;
				//var readerSwf : Reader = new Reader(hxIO);
				var ll : Loader = new Loader();
				ll.contentLoaderInfo.addEventListener(Event.COMPLETE, callback(OnSwfLoaded, _FilePath, _RenderObjectToLoad));
				m_CurrentlyLoading.remove(_Evt.target);
				m_CurrentlyLoading.push(ll.contentLoaderInfo);
				ll.loadBytes(data.getData());
				
			}
		}
	}
	
	private function OnSwfLoaded(_FilePath : String, _RenderObjectToLoad:Array<String>, _Evt : Event):Void
	{
		#if debug
			trace("Swf loaded : " + _FilePath);
		#end
		for (RO in _RenderObjectToLoad)
		{
			var doc :DisplayObjectContainer = Type.createInstance(_Evt.currentTarget.applicationDomain.getDefinition(RO), []);
			if (doc != null)
			{
				#if debug
					trace("    Success(" + RO + ")");
				#end
				var renderObject :RenderObject = new RenderObject();
				renderObject.LoadFromDisplayObjectContainer(doc, _FilePath, _Evt.currentTarget.applicationDomain, RO);
				m_LoadedAssets.set(RO, { ro : renderObject, file : _FilePath, domain : _Evt.currentTarget.applicationDomain});
			}
			#if debug
			else
			{
				trace("    ERROR(" + RO + ")");
			}
			#end
		}
		m_CurrentlyLoading.remove(_Evt.target);
		if (m_CurrentlyLoading.length == 0)
		{
			#if debug
				trace("throw PREPARATION_COMPLETE");
			#end
			this.dispatchEvent(new Event(RessourceManagerEvent.PREPARATION_COMPLETE));
		}
	}
	
	private function OnError(_Evt : Event) : Void
	{
        trace("Error #" + _Evt.toString());
    }
	
	private function OnRessourceDescriptorLoadingComplete(_Evt : Event) : Void
	{
		var xml : Fast = new Fast(Xml.parse(_Evt.currentTarget.data)).node.RessourceManager;
		for (filePath in xml.nodes.File)
		{
			#if debug
				trace("Ressource file to parse : " + filePath.att.path);
			#end
			if (filePath.att.path.substr(filePath.att.path.length - 3) == "xml")
			{
				var ressourceFile : URLLoader = new URLLoader();
				m_CurrentlyLoading.push(ressourceFile);
				ressourceFile.addEventListener(Event.COMPLETE, callback(OnXmlRessourceFileComplete, filePath.att.path));
				ressourceFile.load(new URLRequest(filePath.att.path));
			}
			else if(filePath.att.path.substr(filePath.att.path.length - 3) == "swc")
			{
				var ressourceFile : URLLoader = new URLLoader();
				ressourceFile.dataFormat = URLLoaderDataFormat.BINARY;
				m_CurrentlyLoading.push(ressourceFile);
				ressourceFile.addEventListener(Event.COMPLETE, callback(OnSwcRessourceFileComplete, filePath.att.path));
				ressourceFile.load(new URLRequest(filePath.att.path));
			}
		}
	}
	
	private function OnSwcRessourceFileComplete(_RessourceFilePath:String, _Evt : Event) : Void
	{
		var haxeIOBytesData : BytesData = _Evt.target.data;
		var haxeIOBytes : Bytes = Bytes.ofData(haxeIOBytesData);
		var haxeIO : Input = new BytesInput(haxeIOBytes);
		
		var reader : Reader = new Reader(haxeIO);
		var zipData:Data = reader.read();
		
		for (elem in zipData.iterator())
		{
			
			if (elem.fileName == "catalog.xml")
			{
				// On parse le xml pour savoir quelles classes sont definies dans le swf :
				var fastXml:Fast = new Fast(Xml.parse(cast elem.data).firstElement());
				
				for (xmlChild in fastXml.x.elements())
				{
					trace("elem : " + xmlChild.nodeName);
					if (xmlChild.nodeName == "libraries")
					{
						for (library in xmlChild.elements())
						{
							if (library.nodeName == "library")
							{
								for (scriptElem in library.elements())
								{
									#if debug
										trace("Available asset : " + scriptElem.get("name") + " in : " + _RessourceFilePath);
									#end
									m_AvailableAssets.set(scriptElem.get("name"), { file : _RessourceFilePath, xml : null});
								}
							}
						}
					}
				}
			}
		}
		m_CurrentlyLoading.remove(_Evt.target);
		if (m_CurrentlyLoading.length == 0)
		{
			#if debug
				trace("throw : " + RessourceManagerEvent.INITIALISATION_COMPLETE);
			#end
			this.dispatchEvent(new Event(RessourceManagerEvent.INITIALISATION_COMPLETE));
		}
	}
	
	private function OnXmlRessourceFileComplete(_RessourceFilePath:String, _Evt : Event) : Void
	{
		var xml : Fast = new Fast(Xml.parse(_Evt.currentTarget.data));
		xml = xml.node.Ressource;
		for (RO in xml.nodes.RenderObject)
		{
			#if debug
				trace("Available asset : " + RO.att.name + " in : " + _RessourceFilePath.substr(0, _RessourceFilePath.lastIndexOf("/"))+"/"+xml.att.file);
			#end
			m_AvailableAssets.set(RO.att.name, { file : _RessourceFilePath.substr(0, _RessourceFilePath.lastIndexOf("/"))+"/"+xml.att.file, xml : RO});
		}
		m_CurrentlyLoading.remove(_Evt.target);
		if (m_CurrentlyLoading.length == 0)
		{
			#if debug
				trace("throw : " + RessourceManagerEvent.INITIALISATION_COMPLETE);
			#end
			this.dispatchEvent(new Event(RessourceManagerEvent.INITIALISATION_COMPLETE));
		}
	}
}